/***
 * Copyright (C) Microsoft. All rights reserved.
 * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 *
 * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
 *
 * Builder style class for creating URIs.
 *
 * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
 *
 * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 ****/

#pragma once

#include "base_uri.h"
#include "asyncrt_utils.h"
#include <string>

namespace Pistache
{
/// <summary>
/// Builder for constructing URIs incrementally.
/// </summary>
class _ASYNCRTIMP UriBuilder
{
public:
	/// <summary>
	/// Creates a builder with an initially empty URI.
	/// </summary>
	UriBuilder() = default;

	/// <summary>
	/// Creates a builder with a existing URI object.
	/// </summary>
	/// <param name="uri_str">Encoded string containing the URI.</param>
	explicit UriBuilder(const Uri& uri_obj) : m_uri(uri_obj.m_components) {}

	/// <summary>
	/// Creates a builder with a existing URI string.
	/// </summary>
	/// <param name="uri_str">Encoded string containing the URI.</param>
	explicit UriBuilder(const std::string& uri_str) : m_uri((Uri(uri_str)).m_components) {}

	/// <summary>
	/// Get the scheme component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI scheme as a string.</returns>
	const std::string& scheme() const { return m_uri.m_scheme; }

	/// <summary>
	/// Get the user information component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI user information as a string.</returns>
	const std::string& user_info() const { return m_uri.m_user_info; }

	/// <summary>
	/// Get the host component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI host as a string.</returns>
	const std::string& host() const { return m_uri.m_host; }

	/// <summary>
	/// Get the port component of the URI. Returns -1 if no port is specified.
	/// </summary>
	/// <returns>The URI port as an integer.</returns>
	int port() const { return m_uri.m_port; }

	/// <summary>
	/// Get the path component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI path as a string.</returns>
	const std::string& path() const { return m_uri.m_path; }

	/// <summary>
	/// Get the query component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI query as a string.</returns>
	const std::string& query() const { return m_uri.m_query; }

	/// <summary>
	/// Get the fragment component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI fragment as a string.</returns>
	const std::string& fragment() const { return m_uri.m_fragment; }

	/// <summary>
	/// Set the scheme of the URI.
	/// </summary>
	/// <param name="scheme">Uri scheme.</param>
	/// <returns>A reference to this <c>UriBuilder</c> to support chaining.</returns>
	UriBuilder& set_scheme(const std::string& scheme)
	{
		m_uri.m_scheme = scheme;
		return *this;
	}

	/// <summary>
	/// Set the user info component of the URI.
	/// </summary>
	/// <param name="user_info">User info as a decoded string.</param>
	/// <param name="do_encoding">Specify whether to apply URI encoding to the given string.</param>
	/// <returns>A reference to this <c>UriBuilder</c> to support chaining.</returns>
	UriBuilder& set_user_info(const std::string& user_info, bool do_encoding = false)
	{
		if (do_encoding)
		{
			m_uri.m_user_info = Uri::encode_uri(user_info, Uri::Components::user_info);
		}
		else
		{
			m_uri.m_user_info = user_info;
		}

		return *this;
	}

	/// <summary>
	/// Set the host component of the URI.
	/// </summary>
	/// <param name="host">Host as a decoded string.</param>
	/// <param name="do_encoding">Specify whether to apply URI encoding to the given string.</param>
	/// <returns>A reference to this <c>UriBuilder</c> to support chaining.</returns>
	UriBuilder& set_host(const std::string& host, bool do_encoding = false)
	{
		if (do_encoding)
		{
			m_uri.m_host = Uri::encode_uri(host, Uri::Components::host);
		}
		else
		{
			m_uri.m_host = host;
		}

		return *this;
	}

	/// <summary>
	/// Set the port component of the URI.
	/// </summary>
	/// <param name="port">Port as an integer.</param>
	/// <returns>A reference to this <c>UriBuilder</c> to support chaining.</returns>
	UriBuilder& set_port(int port)
	{
		m_uri.m_port = port;
		return *this;
	}

	/// <summary>
	/// Set the port component of the URI.
	/// </summary>
	/// <param name="port">Port as a string.</param>
	/// <returns>A reference to this <c>UriBuilder</c> to support chaining.</returns>
	/// <remarks>When string can't be converted to an integer the port is left unchanged.</remarks>
	UriBuilder& set_port(const std::string& port);

	/// <summary>
	/// Set the path component of the URI.
	/// </summary>
	/// <param name="path">Path as a decoded string.</param>
	/// <param name="do_encoding">Specify whether to apply URI encoding to the given string.</param>
	/// <returns>A reference to this <c>UriBuilder</c> to support chaining.</returns>
	UriBuilder& set_path(const std::string& path, bool do_encoding = false)
	{
		if (do_encoding)
		{
			m_uri.m_path = Uri::encode_uri(path, Uri::Components::path);
		}
		else
		{
			m_uri.m_path = path;
		}

		return *this;
	}

	/// <summary>
	/// Set the query component of the URI.
	/// </summary>
	/// <param name="query">Query as a decoded string.</param>
	/// <param name="do_encoding">Specify whether apply URI encoding to the given string.</param>
	/// <returns>A reference to this <c>UriBuilder</c> to support chaining.</returns>
	UriBuilder& set_query(const std::string& query, bool do_encoding = false)
	{
		if (do_encoding)
		{
			m_uri.m_query = Uri::encode_uri(query, Uri::Components::query);
		}
		else
		{
			m_uri.m_query = query;
		}

		return *this;
	}

	/// <summary>
	/// Set the fragment component of the URI.
	/// </summary>
	/// <param name="fragment">Fragment as a decoded string.</param>
	/// <param name="do_encoding">Specify whether to apply URI encoding to the given string.</param>
	/// <returns>A reference to this <c>UriBuilder</c> to support chaining.</returns>
	UriBuilder& set_fragment(const std::string& fragment, bool do_encoding = false)
	{
		if (do_encoding)
		{
			m_uri.m_fragment = Uri::encode_uri(fragment, Uri::Components::fragment);
		}
		else
		{
			m_uri.m_fragment = fragment;
		}

		return *this;
	}

	/// <summary>
	/// Clears all Components of the underlying URI in this UriBuilder.
	/// </summary>
	void clear() { m_uri = Details::UriComponents(); }

	/// <summary>
	/// Appends another path to the path of this UriBuilder.
	/// </summary>
	/// <param name="path">Path to append as a already encoded string.</param>
	/// <param name="do_encoding">Specify whether to apply URI encoding to the given string.</param>
	/// <returns>A reference to this UriBuilder to support chaining.</returns>
	UriBuilder& append_path(const std::string& path, bool do_encoding = false);

	/// <summary>
	/// Appends the raw contents of the path argument to the path of this UriBuilder with no separator de-duplication.
	/// </summary>
	/// <remarks>
	/// The path argument is appended after adding a '/' separator without regards to the contents of path. If an empty
	/// string is provided, this function will immediately return without changes to the stored path value. For example:
	/// if the current contents are "/abc" and path="/xyz", the result will be "/abc//xyz".
	/// </remarks>
	/// <param name="path">Path to append as a already encoded string.</param>
	/// <param name="do_encoding">Specify whether to apply URI encoding to the given string.</param>
	/// <returns>A reference to this UriBuilder to support chaining.</returns>
	UriBuilder& append_path_raw(const std::string& path, bool do_encoding = false);

	/// <summary>
	/// Appends another query to the query of this UriBuilder.
	/// </summary>
	/// <param name="query">Query to append as a decoded string.</param>
	/// <param name="do_encoding">Specify whether to apply URI encoding to the given string.</param>
	/// <returns>A reference to this UriBuilder to support chaining.</returns>
	UriBuilder& append_query(const std::string& query, bool do_encoding = false);

	/// <summary>
	/// Appends an relative Uri (Path, Query and fragment) at the end of the current Uri.
	/// </summary>
	/// <param name="relative_uri">The relative Uri to append.</param>
	/// <returns>A reference to this UriBuilder to support chaining.</returns>
	UriBuilder& append(const Uri& relative_uri);

	/// <summary>
	/// Appends another query to the query of this UriBuilder, encoding it first. This overload is useful when building
	/// a query segment of the form "element=10", where the right hand side of the query is stored as a type other than
	/// a string, for instance, an integral type.
	/// </summary>
	/// <param name="name">The name portion of the query string</param>
	/// <param name="value">The value portion of the query string</param>
	/// <returns>A reference to this UriBuilder to support chaining.</returns>
	template<typename T>
	UriBuilder& append_query(const std::string& name, const T& value, bool do_encoding = true)
	{
		if (do_encoding)
			append_query_encode_impl(name, Utility::Conversions::Details::print_string(value));
		else
			append_query_no_encode_impl(name, Utility::Conversions::Details::print_string(value));
		return *this;
	}

	/// <summary>
	/// Combine and validate the URI Components into a encoded string. An exception will be thrown if the URI is
	/// invalid.
	/// </summary>
	/// <returns>The created URI as a string.</returns>
	std::string to_string() const;

	/// <summary>
	/// Combine and validate the URI Components into a URI class instance. An exception will be thrown if the URI is
	/// invalid.
	/// </summary>
	/// <returns>The create URI as a URI class instance.</returns>
	Uri to_uri() const;

	/// <summary>
	/// Validate the generated URI from all existing Components of this UriBuilder.
	/// </summary>
	/// <returns>Whether the URI is valid.</returns>
	bool is_valid();

private:
	void append_query_encode_impl(const std::string& name, const std::string& value);
	void append_query_no_encode_impl(const std::string& name, const std::string& value);

	Details::UriComponents m_uri;
};
}
